package wallettemplate.controls; import org.bitcoinj.core.Address; import org.bitcoinj.uri.BitcoinURI; import wallettemplate.Main; import wallettemplate.utils.GuiUtils; import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeIcon; import javafx.beans.binding.StringExpression; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.Tooltip; import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import net.glxn.qrgen.QRCode; import net.glxn.qrgen.image.ImageType; import java.awt.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; import static javafx.beans.binding.Bindings.convert; /** * A custom control that implements a clickable, copyable Bitcoin address. Clicking it opens a local wallet app. The * address looks like a blue hyperlink. Next to it there are two icons, one that copies to the clipboard and another * that shows a QRcode. */ public class ClickableBitcoinAddress extends AnchorPane { @FXML protected Label addressLabel; @FXML protected ContextMenu addressMenu; @FXML protected Label copyWidget; @FXML protected Label qrCode; protected SimpleObjectProperty<Address> address = new SimpleObjectProperty<>(); private final StringExpression addressStr; public ClickableBitcoinAddress() { try { FXMLLoader loader = new FXMLLoader(getClass().getResource("bitcoin_address.fxml")); loader.setRoot(this); loader.setController(this); // The following line is supposed to help Scene Builder, although it doesn't seem to be needed for me. loader.setClassLoader(getClass().getClassLoader()); loader.load(); AwesomeDude.setIcon(copyWidget, AwesomeIcon.COPY); Tooltip.install(copyWidget, new Tooltip("Copy address to clipboard")); AwesomeDude.setIcon(qrCode, AwesomeIcon.QRCODE); Tooltip.install(qrCode, new Tooltip("Show a barcode scannable with a mobile phone for this address")); addressStr = convert(address); addressLabel.textProperty().bind(addressStr); } catch (IOException e) { throw new RuntimeException(e); } } public String uri() { return BitcoinURI.convertToBitcoinURI(address.get(), null, Main.APP_NAME, null); } public Address getAddress() { return address.get(); } public void setAddress(Address address) { this.address.set(address); } public ObjectProperty<Address> addressProperty() { return address; } @FXML protected void copyAddress(ActionEvent event) { // User clicked icon or menu item. Clipboard clipboard = Clipboard.getSystemClipboard(); ClipboardContent content = new ClipboardContent(); content.putString(addressStr.get()); content.putHtml(String.format("<a href='%s'>%s</a>", uri(), addressStr.get())); clipboard.setContent(content); } @FXML protected void requestMoney(MouseEvent event) { if (event.getButton() == MouseButton.SECONDARY || (event.getButton() == MouseButton.PRIMARY && event.isMetaDown())) { // User right clicked or the Mac equivalent. Show the context menu. addressMenu.show(addressLabel, event.getScreenX(), event.getScreenY()); } } @FXML protected void copyWidgetClicked(MouseEvent event) { copyAddress(null); } @FXML protected void showQRCode(MouseEvent event) { // Serialize to PNG and back into an image. Pretty lame but it's the shortest code to write and I'm feeling // lazy tonight. final byte[] imageBytes = QRCode .from(uri()) .withSize(320, 240) .to(ImageType.PNG) .stream() .toByteArray(); Image qrImage = new Image(new ByteArrayInputStream(imageBytes)); ImageView view = new ImageView(qrImage); view.setEffect(new DropShadow()); // Embed the image in a pane to ensure the drop-shadow interacts with the fade nicely, otherwise it looks weird. // Then fix the width/height to stop it expanding to fill the parent, which would result in the image being // non-centered on the screen. Finally fade/blur it in. Pane pane = new Pane(view); pane.setMaxSize(qrImage.getWidth(), qrImage.getHeight()); final Main.OverlayUI<ClickableBitcoinAddress> overlay = Main.instance.overlayUI(pane, this); view.setOnMouseClicked(event1 -> overlay.done()); } }